#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>

#include "log.h"
#include "misclib.h"

#define DEFAULT_LISTEN_PORT	"echo"

#define MAXLINE		512

struct SG {
	struct {
		const char *program_name;
		const char *bind_addr;
		const char *listen_srvice;
	} main_args;
	int sock;
} SG = {
		.main_args = {
				.program_name = NULL,
				.bind_addr = NULL,
				.listen_srvice = DEFAULT_LISTEN_PORT,
		},
		.sock = -1,
};

static void usage(const char *program_name)
{
    printf("Usage: %s [-b<bind addr>] [-p<listen_port>]\n"
           "\nZ-Gan gateway server daemon\n\n"
           "\t-b <the listen socket bind address>\n"
           "\t   if not specified, will listen all interface.\n"
           "\t-p <network listen port>\n"
           "\t   if not specified, will set as "DEFAULT_LISTEN_PORT".\n",
           program_name);

    exit(EXIT_SUCCESS);
}

static void gateway_getopt(int argc, char *argv[]) {
	int c;

	SG.main_args.program_name = argc > 0 ? argv[0] : "";

	while (1) {
		int option_index = 0;
		static struct option long_options[] = {
				{ "help", 0, 0, 0 },
				{ 0, 0, 0, 0 },
		};

		c = getopt_long(argc, argv, "b:p:", long_options, &option_index);
		if (c == -1)
			break;

		switch (c) {
		case 0:
			switch (option_index) {
			case 0:
			default:
				usage(SG.main_args.program_name);
				break;
			}
			break;
		case 'h':
		case '?':
			usage(SG.main_args.program_name);
			break;
		case 'b':
			SG.main_args.bind_addr = optarg;
			break;
		case 'p':
			SG.main_args.listen_srvice = optarg;
			break;
		default:
			printf("?? getopt returned character code 0%o ??", c);
			break;
		}
	}

	if (optind < argc) {
		printf("non-option ARGV-elements: ");
		while (optind < argc)
			printf("%s ", argv[optind++]);
		printf("\n\n");
		usage(SG.main_args.program_name);
	}
}

/* signal handler */
static void cleanup_sig_handler(int signo)
{
    switch (signo) {
    case SIGINT:
    	LOG(LOG_INFO, "got SIGINT, exiting...");
    	break;
    case SIGTERM:
    	LOG(LOG_INFO, "got SIGTERM, exiting...");
    	break;
    case SIGQUIT:
    	LOG(LOG_INFO, "got SIGQUIT, exiting...");
    	break;
    default:
    	return;
    }

    if (SG.sock >= 0)
    	close(SG.sock);

    exit(EXIT_SUCCESS);
}

static int change_sig_handle(void)
{
	struct sigaction sa;

	sa.sa_handler = cleanup_sig_handler;
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGTERM);
	sigaddset(&sa.sa_mask, SIGQUIT);
	sa.sa_flags = 0;
	if (sigaction(SIGINT, &sa, NULL) < 0) {
		LOG(LOG_ERR, "Can't catch SIGINT: %s", strerror(errno));
		return -1;
	}
	sa.sa_handler = cleanup_sig_handler;
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGINT);
	sigaddset(&sa.sa_mask, SIGQUIT);
	sa.sa_flags = 0;
	if (sigaction(SIGTERM, &sa, NULL) < 0) {
		LOG(LOG_ERR, "Can't catch SIGTERM: %s", strerror(errno));
		return -1;
	}
	sa.sa_handler = cleanup_sig_handler;
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGINT);
	sigaddset(&sa.sa_mask, SIGTERM);
	sa.sa_flags = 0;
	if (sigaction(SIGQUIT, &sa, NULL) < 0) {
		LOG(LOG_ERR, "Can't catch SIGQUIT: %s", strerror(errno));
		return -1;
	}

	sa.sa_handler = SIG_IGN;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	if (sigaction(SIGPIPE, &sa, NULL) < 0) {
		LOG(LOG_ERR, "Can't ignore SIGPIPE: %s", strerror(errno));
		return -1;
	}

	return 0;
}

static int create_listen_socket(const char *host, const char *srv, socklen_t *addrlen)
{
	int sockfd;

	int err;
	struct addrinfo     *ailist, *aip;
	struct addrinfo     hint;

	memset(&hint, 0, sizeof(hint));
	hint.ai_flags = AI_PASSIVE;
	hint.ai_family = AF_UNSPEC;
	hint.ai_socktype = SOCK_STREAM;

	if ((err = getaddrinfo(host, srv, &hint, &ailist)) < 0) {
		LOG(LOG_ERR, "create_listen_socket error: "
				"getaddrinfo(%s, %s, &hint, &ailist) error: %s",
				host, srv, gai_strerror(err));
		return -1;
	}

	for (sockfd = -1, aip = ailist; aip; aip = ailist->ai_next) {
		sockfd = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
		if (sockfd < 0)
			continue;

		int reuse = 1;
		setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

		if (bind(sockfd, aip->ai_addr, aip->ai_addrlen) == 0)
			break;

		close(sockfd);
		sockfd = -1;
	}

	if (sockfd < 0) {
		LOG(LOG_ERR, "create_listen_socket error for %s, %s", host, srv);
		goto FREEADDRINFO;
	}

	if (listen(sockfd, 0) < 0) {
		LOG(LOG_ERR, "create_listen_socket: listen() error: %s", strerror(errno));
		close(sockfd);
		sockfd = -1;
		goto FREEADDRINFO;
	}

	if (addrlen)
		*addrlen = aip->ai_addrlen;

FREEADDRINFO:
	freeaddrinfo(ailist);

	return sockfd;
}

void process_one_connection(int sock)
{
	ssize_t n;
	char buf[MAXLINE];

again:
	while ((n = read(sock, buf, MAXLINE)) > 0)
		if (writen(sock, buf, n) != n)
			LOG(LOG_ERR, "writen error: %s\n", strerror(errno));
	if (n < 0 && errno == EINTR)
		goto again;
	else if (n < 0)
		LOG(LOG_ERR, "str_echo: read error: %s\n", strerror(errno));
}

int main(int argc, char *argv[])
{
	int csock;

	gateway_getopt(argc, argv);

	/* Initialize the log file. */
	OPENLOG();

	if (change_sig_handle() < 0)
		goto EXIT;

	SG.sock = create_listen_socket(SG.main_args.bind_addr,
			SG.main_args.listen_srvice, NULL);
	if (SG.sock < 0)
		goto EXIT;
	log_listen_socket_info(SG.sock);

	while (1) {
		csock = accept(SG.sock, NULL, NULL);
		if (csock < 0) {
			int se = errno;
			LOG(LOG_ERR, "accept error: %s", strerror(errno));
			switch (se) {
			case EAGAIN:
#if EAGAIN != EWOULDBLOCK
			case EWOULDBLOCK:
#endif /* #if EAGAIN != EWOULDBLOCK */
			case ECONNABORTED: case EINTR: case EMFILE: case ENFILE:
			case ENOBUFS: case ENOMEM: case ETIMEDOUT:
				sleep(1);
				continue;
			default:
				goto EXIT;
			}
		}
		log_connect_info(csock);

		process_one_connection(csock);

		close(csock);
	}

EXIT:
	if (SG.sock >= 0)
		close(SG.sock);

	exit(EXIT_SUCCESS);
}
